﻿using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Starter3DGameEngine;

namespace Starter3DGame
{
    public partial class GamePage : PhoneApplicationPage
    {
        #region fields
        ContentManager contentManager;
        GameTimer timer;
        SpriteBatch spriteBatch;

        UIElementRendererHelper uiRenderer;

        GraphicsDevice graphics;

        // The aspect ratio determines how to scale 3d to 2d projection.
        float aspectRatio;

        SpriteFont Font;
        Random random = new Random();

        //Player Ship
        Ship ship = new Ship();

        //Asteroid Template
        Asteroid[] asteroidList;
        Matrix[] AsteroidsTransforms;
        Model AsteroidModel;

        //Bullet Template
        Model BulletModel;
        Texture2D BulletTexture;
        Matrix[] bulletTransforms;
        //Background
        Texture2D stars;



        public int Score = 0;
        public float TimetoLive = 0;
        public int PlayerLives = 0;
        public int Level = 1;

        #endregion

        public GamePage()
        {
            InitializeComponent();

            // Get the content manager from the application
            contentManager = (Application.Current as App).Content;

            // Create a timer for this page
            timer = new GameTimer();
            timer.UpdateInterval = TimeSpan.FromTicks(333333);
            timer.Update += OnUpdate;
            timer.Draw += OnDraw;

            graphics = SharedGraphicsDeviceManager.Current.GraphicsDevice;

            this.Loaded += new RoutedEventHandler(GamePage_Loaded);

           
        }

        void GamePage_Loaded(object sender, RoutedEventArgs e)
        {
            AdRotatorControl.Invalidate();
        }

        

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // Set the sharing mode of the graphics device to turn on XNA rendering
            graphics.SetSharingMode(true);
            Initialize();
            
            // TODO: use this.content to load your game content here
            // Load Font
            Font = contentManager.Load<SpriteFont>(@"Fonts\SpriteFont1");

            //Setup Camera
            aspectRatio = graphics.Viewport.AspectRatio;

            GameConstants.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
              MathHelper.ToRadians(45.0f), aspectRatio,
              GameConstants.CameraHeight - 100.0f,
              GameConstants.CameraHeight + 100.0f);

            GameConstants.viewMatrix = Matrix.CreateLookAt(GameConstants.cameraPosition, Vector3.Zero, Vector3.Up);

            //Load Models
            ship.Model = contentManager.Load<Model>(@"Models\p1_wedge");
            ship.Transforms = SetupEffectDefaults(ship.Model);

            AsteroidModel = contentManager.Load<Model>(@"Models\asteroid1");
            AsteroidsTransforms = SetupEffectDefaults(AsteroidModel);

            BulletModel = contentManager.Load<Model>(@"Models\pea_proj");
            BulletTexture = contentManager.Load<Texture2D>(@"Textures\pea_proj");
            bulletTransforms = SetupEffectDefaults(BulletModel);
            BulletModel = SetupModelTexture(BulletModel, BulletTexture);

            //Load Background
            stars = contentManager.Load<Texture2D>(@"Textures\B1_stars");

            //Load Audio
            AudioEngine.LoadContent(contentManager);

            AudioEngine.StartBackgroundMusic();

            // Start the timer
            timer.Start();

            base.OnNavigatedTo(e);

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected void Initialize()
        {

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(graphics);

            // Construct after enabling XNA rendering
            uiRenderer = new UIElementRendererHelper(this);

            GameConstants.viewport = graphics.Viewport;

            ResetAsteroids();

            PlayerLives = GameConstants.PlayerLives;

        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            AdRotatorControl.Dispose();
            // Stop the timer
            timer.Stop();

            contentManager.Unload();

            // Dispose before turning off XNA rendering
            uiRenderer.Dispose();

            // Set the sharing mode of the graphics device to turn off XNA rendering
            graphics.SetSharingMode(false);

            base.OnNavigatedFrom(e);
        }

        /// <summary>
        /// Allows the page to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        private void OnUpdate(object sender, GameTimerEventArgs e)
        {
            // TODO: Add your update logic here
            
            // Allows the game to exit
            float time = (float)e.ElapsedTime.TotalMilliseconds;
            Input.Update();

            //Is ship still alive, if so update.
            if (ship.isActive)
                ship.Update(e.ElapsedTime);
            else
            {
                time = time / 100;
                if (time >= TimetoLive)
                {
                    ship.isActive = true;
                    AudioEngine.HyperSpace.Play();
                }
                else
                {
                    TimetoLive -= time;
                }
            }

            // Are there any asteroids left, if so update
            bool Asteroidsleft = false;
            for (int i = 0; i < asteroidList.Length; i++)
            {
                if (asteroidList[i].isActive)
                {
                    asteroidList[i].Update(e.ElapsedTime);
                    Asteroidsleft = true;
                }
            }
            if (!Asteroidsleft)
            {
                Level++;
                Level = (int)MathHelper.Clamp(Level, 1, GameConstants.MaxLevel);
                ResetAsteroids();

            }


            for (int i = 0; i < GameConstants.NumBullets; i++)
            {
                if (GameConstants.bulletList[i].isActive)
                {
                    GameConstants.bulletList[i].Update(e.ElapsedTime);
                }
            }

            CheckCollisions();
        }

        /// <summary>
        /// Allows the page to draw itself.
        /// </summary>
        private void OnDraw(object sender, GameTimerEventArgs e)
        {
            uiRenderer.PreRender();

            graphics.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            spriteBatch.Begin();
            spriteBatch.Draw(stars, GameConstants.viewport.TitleSafeArea, Color.White);
            spriteBatch.DrawString(Font, "Hello World", new Vector2(10, 10), Color.Red);
            spriteBatch.DrawString(Font, "Lives: " + PlayerLives, new Vector2(10, 40), Color.Red);
            spriteBatch.DrawString(Font, "Score: " + Score, new Vector2(10, 70), Color.Red);
            spriteBatch.DrawString(Font, "Level: " + Level, new Vector2(10, 100), Color.Green);
            if (!ship.isActive) spriteBatch.DrawString(Font, "Time to Live: " + (int)(TimetoLive + 10) / 10, new Vector2(10, 130), Color.Red);
            spriteBatch.End();

            graphics.BlendState = BlendState.Opaque;
            graphics.DepthStencilState = DepthStencilState.Default;
            graphics.SamplerStates[0] = SamplerState.LinearWrap;

            if (ship.isActive)
            {
                DrawModel(ship.Model, ship.TransformMatrix, ship.Transforms);
            }

            for (int i = 0; i < asteroidList.Length; i++)
            {
                if (asteroidList[i].isActive)
                {
                    DrawModel(AsteroidModel, asteroidList[i].TransformMatrix, AsteroidsTransforms);
                }
            }

            for (int i = 0; i < GameConstants.NumBullets; i++)
            {
                if (GameConstants.bulletList[i].isActive)
                {
                    Matrix bulletTransform =
                      Matrix.CreateTranslation(GameConstants.bulletList[i].Position);
                    DrawModel(BulletModel, bulletTransform, bulletTransforms);
                }
            }

            uiRenderer.Draw();
        }

        #region Game Functions
        private void ResetAsteroids()
        {
            float xStart;
            float yStart;
            asteroidList = new Asteroid[GameConstants.NumAsteroids * Level];
            for (int i = 0; i < GameConstants.NumAsteroids * Level; i++)
            {
                asteroidList[i] = new Asteroid();
                if (random.Next(2) == 0)
                {
                    xStart = (float)-GameConstants.PlayfieldSizeX;
                }
                else
                {
                    xStart = (float)GameConstants.PlayfieldSizeX;
                }
                yStart = (float)random.NextDouble() * GameConstants.PlayfieldSizeY;
                asteroidList[i].Position = new Vector3(xStart, yStart, 0.0f);
                double angle = random.NextDouble() * 2 * Math.PI;
                asteroidList[i].Direction.X = -(float)Math.Sin(angle);
                asteroidList[i].Direction.Y = (float)Math.Cos(angle);
                asteroidList[i].Speed = GameConstants.AsteroidMinSpeed +
                   (float)random.NextDouble() * GameConstants.AsteroidMaxSpeed;
                asteroidList[i].Rotation.X = (float)random.NextDouble();
                asteroidList[i].Rotation.Y = (float)random.NextDouble();
                asteroidList[i].Rotation.Z = (float)random.NextDouble();
                asteroidList[i].isActive = true;
            }
        }

        private Matrix[] SetupEffectDefaults(Model myModel)
        {
            Matrix[] absoluteTransforms = new Matrix[myModel.Bones.Count];
            myModel.CopyAbsoluteBoneTransformsTo(absoluteTransforms);

            foreach (ModelMesh mesh in myModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.Projection = GameConstants.projectionMatrix;
                    effect.View = GameConstants.viewMatrix;
                    effect.PreferPerPixelLighting = false;
                }
            }
            return absoluteTransforms;
        }

        private Model SetupModelTexture(Model myModel, Texture2D myTexture)
        {

            foreach (ModelMesh mesh in myModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.TextureEnabled = true;
                    effect.Texture = myTexture;
                }
            }
            return myModel;

        }

        public static void DrawModel(Model model, Matrix modelTransform, Matrix[] absoluteBoneTransforms)
        {
            //Draw the model, a model can have multiple meshes, so loop
            foreach (ModelMesh mesh in model.Meshes)
            {
                //This is where the mesh orientation is set
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = absoluteBoneTransforms[mesh.ParentBone.Index] * modelTransform;
                }
                //Draw the mesh, will use the effects set above.
                mesh.Draw();
            }
        }

        public void CheckCollisions()
        {
            //Check Ship against Asteroids
            if (ship.isActive)
            {
                for (int i = 0; i < asteroidList.Length; i++)
                {
                    if (asteroidList[i].isActive)
                    {
                        //Test Distance
                        float distance = Vector3.Distance(ship.Position, asteroidList[i].Position);
                        if (distance <= GameConstants.ShipCollisionSize || distance <= GameConstants.AsteroidCollisionSize)
                        {
                            //Collided
                            DestroyShip();
                            asteroidList[i].isActive = false;
                            break;
                        }
                    }
                }
            }

            //Check Ship against Bullets
            if (ship.isActive)
            {
                for (int i = 0; i < GameConstants.NumBullets; i++)
                {
                    if (GameConstants.bulletList[i].isActive)
                    {
                        //Test Distance
                        float distance = Vector3.Distance(ship.Position, GameConstants.bulletList[i].Position);
                        if (distance <= GameConstants.ShipCollisionSize || distance <= GameConstants.BulletCollisionSize)
                        {
                            //Collided
                            DestroyShip();
                            GameConstants.bulletList[i].isActive = false;
                            break;
                        }
                    }
                }
            }

            //Check Asteroids against Asteroids
            for (int i = 0; i < asteroidList.Length; i++)
            {
                if (asteroidList[i].isActive)
                {
                    for (int j = 0; j < asteroidList.Length; j++)
                    {
                        if (asteroidList[j].isActive && j > i)
                        {
                            //Test Distance
                            float distance = Vector3.Distance(asteroidList[j].Position, asteroidList[i].Position);
                            if (distance <= GameConstants.AsteroidCollisionSize)
                            {
                                //Collided
                                BounceAsteroid();
                            }

                        }
                    }

                }
            }

            //Check Asteroids agsinst Bullets
            for (int i = 0; i < asteroidList.Length; i++)
            {
                if (asteroidList[i].isActive)
                {
                    for (int j = 0; j < GameConstants.NumBullets; j++)
                    {
                        if (GameConstants.bulletList[j].isActive)
                        {
                            //Test Distance
                            float distance = Vector3.Distance(asteroidList[i].Position, GameConstants.bulletList[j].Position);
                            if (distance <= GameConstants.AsteroidCollisionSize || distance <= GameConstants.BulletCollisionSize)
                            {
                                //Collided
                                asteroidList[i].isActive = false;
                                GameConstants.bulletList[j].isActive = false;
                                AudioEngine.ExplosionAsteroid.Play();
                                Score++;
                            }

                        }
                    }
                }
            }
        }

        public void DestroyShip()
        {
            ship.isActive = false;
            ship.Position = Vector3.Zero;
            ship.Velocity = Vector3.Zero;
            AudioEngine.ExplosionShip.Play();
            PlayerLives--;
            TimetoLive = GameConstants.ShipSpawnTime;
        }

        public void BounceAsteroid()
        {
            //Work out new direction for each asteroid and send them on their way
            //Marker for future consideration
        }
        #endregion
    }
}